package com.compress4j.arithmetic.ppm;

import java.util.Set;

public class PPMNode<T>{

	private PPMNode<T> firstChild;
	
	private PPMNode<T> nextBrother;
	
	private int weightAsItem;
	
	private final T item;

	public PPMNode(T item, int weightAsItem) {
		this.item = item;
		this.weightAsItem = weightAsItem;
	}
	/**
	 * to help extending this class without having to recode addWeight
	 * @param o
	 * @param w
	 * @return
	 */
	protected PPMNode<T> newInstance(T o, int w) {
		return new PPMNode<T>(o,w);
	}
	/**
	 * return item where weight has been added
	 * and which is the next to use (magic of PPM)
	 * @param o
	 * @param w
	 * @return
	 */
	public PPMNode<T> addWeight(T o, int w) {
		PPMNode<T> crawler = this.firstChild;
		if(crawler == null){
			this.firstChild = newInstance(o, w);
			return this.firstChild;
		}
		
		PPMNode<T> previous = null;
		//soon or later we will get the good one or null
		while(true){
			if(crawler.item.equals(o)){
				crawler.weightAsItem += w;
				//pseudo sorting help performance, true sorting does not
				//if current node weight become bigger than previousnode, we put current node as firstNode
				//TODO check if switching it with previous instead would not be better
				if(previous!=null && previous.weightAsItem<crawler.weightAsItem){
					previous.nextBrother = crawler.nextBrother;
					crawler.nextBrother = this.firstChild;
					this.firstChild = crawler; 
				}
				return crawler;
			}
			if(crawler.nextBrother==null){
				PPMNode<T> newNode = newInstance(o, w);
				crawler.nextBrother = newNode;
				return newNode;
			}
			previous = crawler;
			crawler = crawler.nextBrother;
		}
		
	}
	public boolean fillRange(T o, Set<T> excluded,RangeBean bean) {
		PPMNode<T> crawler = this.firstChild;
		int start = 0;
		int length = 0;
		int total = 0;
		int nbitem = 0;
		while(crawler!=null){
			if(!excluded.contains(crawler.item)){
				if(length==0 && crawler.item.equals(o)){
					start = total;
					length = crawler.weightAsItem;
				}
				total += crawler.weightAsItem;
			}	
			nbitem ++;
			crawler = crawler.nextBrother;
		}
		int trueTotal = total+nbitem;
		//if I've not found I use the escape interval  
		if(length==0){
			bean.set(total,trueTotal,trueTotal);
			return false;
		}else{
			bean.set(start,start+length,trueTotal);
			return true;
		}
	}
	/**
	 * 
	 * @param excluded
	 * @return the total of weight of non excluded items
	 */
	public int getTotal(Set<T> excluded) {
		PPMNode<T> crawler = this.firstChild;
		int total = 0;
		int nbitem = 0;
		while(crawler!=null){
			if(!excluded.contains(crawler.item)){
				total+=crawler.weightAsItem;
			}
			nbitem++;
			crawler = crawler.nextBrother;
		}
		return total+nbitem;
	}
	/**
	 * return the item associated with this weight and fill the bean with the corresponding interval
	 * @param weightPosition
	 * @param excluded
	 * @param bean
	 * @return
	 */
	public T getItem(int weightPosition, int total, Set<T> excluded, RangeBean bean) {
		PPMNode<T> crawler = this.firstChild;
		int startInterval = 0;
		while(crawler!=null){
			if(!excluded.contains(crawler.item)){
				startInterval+= crawler.weightAsItem;
				if(startInterval>weightPosition){
					bean.set(startInterval- crawler.weightAsItem, startInterval,total);
					return crawler.item;
				}
			}
			crawler = crawler.nextBrother;
		}
		bean.set(startInterval, total,total);
		return null;
	}

	public void excludeAllChild(Set<T> excluded){
		PPMNode<T> crawler = this.firstChild;
		while(crawler!=null){
			excluded.add(crawler.item);
			crawler = crawler.nextBrother;
		}
	}
/**
 * 
 * @return true if 0 or 1 subnodes
 */
	public boolean onePossibility(){
		if(firstChild==null) return true;
		if(firstChild.nextBrother==null) return true;
		return false;
	}
	
	public PPMNode<T> getFirstChild() {
		return firstChild;
	}
	
	public PPMNode<T> getNextBrother() {
		return nextBrother;
	}
	
	public void setFirstChild(PPMNode<T> firstChild) {
		this.firstChild = firstChild;
	}
	public void setNextBrother(PPMNode<T> nextBrother) {
		this.nextBrother = nextBrother;
	}
	
	public int getWeight() {
		return weightAsItem;
	}
	public void setWeight(int weightAsItem) {
		this.weightAsItem = weightAsItem;
	}
	public T getItem() {
		return item;
	}
	/**
	 * for debugging purpose
	 */
	@Override
	public String toString() {
		PPMNode<T> crawler = this.firstChild;
		StringBuilder b = new StringBuilder();
		b.append('[');
		
		while(crawler!=null){
			b.append(crawler.item);
			b.append(':');
			b.append(crawler.weightAsItem);
			b.append(" ,");
			crawler = crawler.nextBrother;
		}
		
		b.append(']');
		return b.toString();
	}
	
	
}
